package it.pir.es03ChatUDP;

import java.io.*;
import java.net.*;
import java.util.HashMap;
import java.util.Map;



public class Server {

	private static Map<String,DataClient> clients;
	
	
	private static DatagramSocket socket;
	private static ObjectInputStream objectInputStream;
	private static ChatWindow chat;
	private static final int PORT=7777;
	private static final int MAX=1000;
	private static Thread mainThread;


	public static void initServer() throws IOException {
		
		clients = new HashMap<String,DataClient>();
		
		socket = new DatagramSocket(PORT);
		chat = new ChatWindow();
        chat.setVisible(true);
        chat.setSize(800, 600);
	}
	
	public static void addClient(String nickname, DataClient newclient) {
		clients.put(nickname, newclient);
	}
	
	public static void removeClient(String nickname) {
		clients.remove(nickname);
	}
	
	public static void broadcastSend(String nickname, Message message) throws IOException {
        chat.addMessage(message.getNickname() + ": "+ message.getMessage());
        
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(bos);
		
		for(Map.Entry<String, DataClient> entry : clients.entrySet()) {
			if(!nickname.equals(entry.getKey())) {
				
				//Prendo il msg passato per parametro e lo scrivo sullo stream di output
				oos.writeObject(message);
				byte bufferResponse[] = bos.toByteArray();
				
				//prelevo dalla Map l'oggetto DataClient corrispondente a quel client e recupero l'IP e la porta utilizzata
				DatagramPacket packetResponse = new DatagramPacket(bufferResponse,bufferResponse.length,entry.getValue().getAddress(),entry.getValue().getPort());
				socket.send(packetResponse);
				
				
				
			}
		}
	}
	
	public static void main(String[] args) throws IOException {
		initServer();
		
		/* Uccidiamo il thread se avviene la disconnessione del server e lo comunichiamo ai client*/ 
		mainThread = Thread.currentThread();
		
		Runtime.getRuntime().addShutdownHook(new Thread(){
			public void run(){
				try {
					broadcastSend("SERVER: ", new Message("SERVER", "Il Server si è disconnesso.", Message.DISCONNECT));
					mainThread.interrupt();
				} catch (IOException e) {
					e.printStackTrace();
				}

			}
			
		});
		
		byte[] bufferReceiver = new byte[MAX];

		//Si puo portare fuori dal while?
		DatagramPacket packetReceived = new DatagramPacket(bufferReceiver,bufferReceiver.length);
		
		while(true) {
			try {
				DataClient newclient;
				
				/*A differenza della chat TCP qui si utilizza la stessa socket per tutti i client*/
				socket.receive(packetReceived);
				
				ByteArrayInputStream bis = new ByteArrayInputStream(bufferReceiver,0,packetReceived.getLength());
				objectInputStream = new ObjectInputStream(bis); 
				Message message = (Message)objectInputStream.readObject(); 
				
				
				if(message.getType()==Message.AUTHENTICATION) {
					
					
					
					ByteArrayOutputStream bos = new ByteArrayOutputStream();
					ObjectOutputStream oos = new ObjectOutputStream(bos);
					
					//Se il nickname è già in uso si invia un messaggio di errore al client in questione
					if(clients.containsKey(message.getNickname())) {
						
						//Creo un nuovo messaggio con nickname "SERVER" e type "AUTHENTICATION"
						Message m1 = new Message("SERVER","ERRORE! Nickname già in uso!", Message.DISCONNECT);
						oos.writeObject(m1);
						oos.flush();
						
						byte bufferResponse[] = bos.toByteArray();
						DatagramPacket packetResponse = new DatagramPacket(bufferResponse,bufferResponse.length,packetReceived.getAddress(),packetReceived.getPort());
						socket.send(packetResponse);
						
						
					} else {
						
						//Creo un oggetto DataClient
						newclient = new DataClient(packetReceived.getAddress(), packetReceived.getPort());
						
						//Aggiungi il client alla Map, prelevando il nickname dal messaggio ed inserendo l'oggetto DataClient corrispondente come value
						addClient(message.getNickname(), newclient); 
						
						//Risposta per il client
						Message m2 = new Message("SERVER", "Autenticazione effettuata! Ora sei connesso come " + message.getNickname(), Message.AUTHENTICATION); 
						
						oos.writeObject(m2);
						oos.flush();
						
						byte bufferResponse[] = bos.toByteArray();
						DatagramPacket packetResponse = new DatagramPacket(bufferResponse,bufferResponse.length,packetReceived.getAddress(),packetReceived.getPort());
						socket.send(packetResponse);
						
					
						// Invia una notifica a tutti gli altri per notificare che l'utente è entrato in chat
						broadcastSend(message.getNickname(), new Message("SERVER", "L'utente " + message.getNickname() + " è entrato nella chat.", Message.MESSAGE)); 
						
						//DA MODIFICARE se si vuole utilizzare un solo thread
						//ClientHandler ch = new ClientHandler(message.getNickname(), this, newclient, socket);
						//Thread t = new Thread(ch);
						//t.start();
					}
					
				//Su questo if il msg è di tipo MESSAGE
				} else if(message.getType()==Message.MESSAGE) {
					
					broadcastSend(message.getNickname(), message);
				
				} else if(message.getType()==Message.DEAUTHENTICATION){
					
					removeClient(message.getNickname());
					broadcastSend(message.getNickname(), new Message("SERVER", "L'utente " + message.getNickname() + " è uscito dalla chat.", Message.MESSAGE));

				}
				
				//Se arriva un msg di tipo MESSAGE e l'utente non si è iscritto
				else{
					
					// ERRORE: Il primo messaggio deve essere di autenticazione!
					System.out.println("ERRORE!!! L'utente " + message.getNickname() + " ha inviato un messaggio non valido!");
				}
			} catch (IOException e) {
				System.out.println("ERRORE di comunicazione classe Server");
			} catch (ClassNotFoundException e) {
				System.out.println("ERRORE Server classe non trovata");
			}
		}
	}

}
